from collections import defaultdict
import os
import re
import subprocess
import sys

import term.framework


class PreCommitChecker(term.framework.Application):

    def __init__(self):
        self.error_recorder = ErrorRecorder()

    def configure_parser(self, parser):
        parser.add_option('-r',
                          dest='revision',
                          action="store_true",
                          default=False)

    def run(self, opts, args):
        self.version_kind = self.version_option_for_svnlook_command(opts)
        (self.version, self.path, self.ptrn) = args[1:4]
        self.path = args[2]
        self.ptrn = args[3]
        file_names = self.run_svnlook_changed()
        self.record_errors(file_names, self.path, self.ptrn)
        self.assert_no_errors()

    def assert_no_errors(self):
        if self.error_recorder.has_errors():
            print >> sys.stderr, self.error_recorder.message()
            sys.exit(1)

    def version_option_for_svnlook_command(self, options):
        if options.revision:
            return '-r'
        else:
            return '-t'

    def svnlook_command(self, *pargs):
        return ["/usr/bin/svnlook"] + list(pargs)

    log_ptrn = re.compile('^(\S)\s+(.+)\n$')

    def run_svnlook_changed(self):
        cmd = self.svnlook_command(self.version_kind, self.version, "changed", self.path)
        return self.parsed_svnlook_changed(self.run_svnlook(cmd))

    def run_svnlook_cat(self, filename):
        cmd = self.svnlook_command(self.version_kind, self.version, "cat", self.path, filename)
        return self.run_svnlook(cmd)

    def run_svnlook(self, cmd):
        svnlook = subprocess.Popen(cmd, stdout=subprocess.PIPE)
        try:
            return svnlook.stdout.readlines()
        finally:
            svnlook.stdout.close()

    def parsed_svnlook_changed(self, lines):
        action_file_pairs = []
        for line in lines:
            log_parts = self.log_ptrn.search(line)
            if log_parts:
                action_file_pair = (log_parts.group(1), log_parts.group(2))
                action_file_pairs.append(action_file_pair)
        return action_file_pairs

    python_file_ptrn = re.compile(r'.+\.py$')
    
    def record_errors(self, action_file_pairs, file_root, file_ptrn):
        file_start_ptrn = re.compile("^" + file_ptrn)
        for (action, file_name) in action_file_pairs:
            if self.file_should_be_checked(action, file_name,
                                           user_ptrn = file_start_ptrn,
                                           sys_ptrn = self.python_file_ptrn):
                    self.check_file(file_name, self.file_contents(file_name))
#        print >> sys.stderr, "CHECK WAS PERFORMED"

    def file_should_be_checked(self, action, filename, user_ptrn, sys_ptrn):
        return (self.is_checkable(action)
           and bool(user_ptrn.search(filename))
           and bool(sys_ptrn.search(filename)))

    def is_checkable(self, action):
        return action == 'U' or action == 'A'

    def file_contents(self, filename):
        return self.run_svnlook_cat(filename)
    
    def check_file(self, filename, lines):
        """This method is implemented in each descendent.
        
        It performs the needed check. When it encounters an error, that
        error should be recorded in self.error_recorder.
        
        """
        pass


class ErrorRecorder(object):
    
    def __init__(self):
        self.errors = defaultdict(list)
    
    def has_errors(self):
        return bool(self.errors)

    def add(self, filename, error_msg):
        self.errors[filename].append(error_msg)
    
    def message(self):
        msgs = [self.per_file_message(x) for x in sorted(self.errors.keys())]
        return os.linesep.join(msgs)

    def per_file_message(self, filename):
        msg = filename + ":" + os.linesep
        for m in self.errors[filename]:
            msg += m + os.linesep
        return msg


